
#include <AudioFileSourceLittleFS.h>

// NEO PIXEL BUS
#include <NeoPixelAnimator.h>
#include <NeoPixelBus.h>

// ESP8266 AUDIO
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2SNoDAC.h"
#include "app_config.hpp"
#include "io_expansion.hpp"


// =======================  RGB  ========================

// NeoPixelFunFadeInOut
// This example will randomly pick a color and fade all pixels to that color, then
// it will fade them to black and restart over
//
// This example demonstrates the use of a single animation channel to animate all
// the pixels at once.
//

constexpr u8 PIXEL_COUNT = 6;        // make sure to set this to the number of pixels in your strip
constexpr u8 PIXEL_PIN = PIN_I2S;    // make sure to set this to the correct pin, ignored for Esp8266
constexpr u8 ANIMATION_CHANNEL = 1;  // we only need one as all the pixels are animated at once


using StripType = NeoPixelBus<NeoGrbFeature, NeoWs2812xMethod>;

// class I2sStrip : public StripType {

//     I2sStrip(uint16_t countPixels, uint8_t pin) : StripType(countPixels, pin) {}
// };

static StripType *strip = nullptr;
// For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use.
// There are other Esp8266 alternative methods that provide more pin options, but also have
// other side effects.
// for details see wiki linked here https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods

static NeoPixelAnimator animations(ANIMATION_CHANNEL);  // NeoPixel animation management object

boolean fadeToColor = true;  // general purpose variable used to store effect state


// what is stored for state is specific to the need, in this case, the colors.
// basically what ever you need inside the animation update function
struct MyAnimationState {
    RgbColor StartingColor;
    RgbColor EndingColor;
};


// one entry per pixel to match the animation timing manager
MyAnimationState animationState[ANIMATION_CHANNEL];

void SetRandomSeed() {
    uint32_t seed;

    // random works best with a seed that can use 31 bits
    // analogRead on a unconnected pin tends toward less than four bits
    seed = analogRead(0);
    delay(1);

    for (int shifts = 3; shifts < 31; shifts += 3) {
        seed ^= analogRead(0) << shifts;
        delay(1);
    }

    randomSeed(seed);
}


// simple blend function
void BlendAnimUpdate(const AnimationParam &param) {
    // this gets called for each animation on every time step
    // progress will start at 0.0 and end at 1.0
    // we use the blend function on the RgbColor to mix
    // color based on the progress given to us in the animation
    RgbColor updatedColor = RgbColor::LinearBlend(
        animationState[param.index].StartingColor,
        animationState[param.index].EndingColor,
        param.progress);

    // apply the color to the strip
    for (uint16_t pixel = 0; pixel < PIXEL_COUNT; pixel++) {
        strip->SetPixelColor(pixel, updatedColor);
    }
}


void FadeInFadeOutRinseRepeat(float luminance) {
    if (fadeToColor) {
        // Fade upto a random color
        // we use HslColor object as it allows us to easily pick a hue
        // with the same saturation and luminance so the colors picked
        // will have similiar overall brightness
        RgbColor target = HslColor(random(360) / 360.0f, 1.0f, luminance);
        uint16_t time = random(800, 2000);

        animationState[0].StartingColor = strip->GetPixelColor<RgbColor>(0);
        animationState[0].EndingColor = target;

        animations.StartAnimation(0, time, BlendAnimUpdate);
    }
    else {
        // fade to black
        uint16_t time = random(600, 700);

        animationState[0].StartingColor = strip->GetPixelColor<RgbColor>(0);
        animationState[0].EndingColor = RgbColor(0);

        animations.StartAnimation(0, time, BlendAnimUpdate);
    }

    // toggle to the next effect state
    fadeToColor = !fadeToColor;
}


/**
 * @brief I2S-DAT 引脚（GPIO3）上挂着RGB 和蜂鸣器，二者同时启用会相互干扰
 *
 * NeoPixelBus 和ESP8266Audio 都要使用I2S，而且暂时似乎没办法分时复用，所以每次上电后只能要么启用蜂鸣器，
 * 要么启用RGB
 *
 * @param to_enable
 */
void enable_rgb_via_i2s(bool to_enable) {
    u8 pin_mask = static_cast<u8>(_BV(EX_RGB_EN));
    if (to_enable) {
        pin_mask = ~pin_mask;
        set_ex1(0, pin_mask);
    }
    else {
        set_ex1(pin_mask, 0xff);
    }
}


/**
 * @brief I2S-DAT 引脚（GPIO3）上挂着RGB 和蜂鸣器，二者同时启用会相互干扰
 *
 * @param to_enable
 */
void enable_buzzer_via_i2s(bool to_enable) {
    u8 pin_mask = static_cast<u8>(_BV(EX_LPF_EN));
    if (to_enable) {
        pin_mask = ~pin_mask;
        set_ex1(0, pin_mask);
    }
    else {
        set_ex1(pin_mask, 0xff);
    }
}


void init_rgb() {
    if (strip == nullptr) {
        strip = new StripType{PIXEL_COUNT, PIXEL_PIN};
    }
    enable_rgb_via_i2s(true);
    strip->Begin();
    strip->Show();

    SetRandomSeed();
}


/**
 * @brief 释放掉NeoPixelBus 对DMA 和I2S 的控制
 *
 */
void deinit_rgb() {
    if (strip != nullptr) {
        delete (strip);
        strip = nullptr;
    }
    enable_rgb_via_i2s(false);
}


TimeType task_rgb_animation() {
    if (animations.IsAnimating()) {
        // the normal loop just needs these two to run the active animations
        animations.UpdateAnimations();
        strip->Show();
    }
    else {
        // no animation runnning, start some
        //
        FadeInFadeOutRinseRepeat(0.2f);  // 0.0 = black, 0.25 is normal, 0.5 is bright
    }

    enable_rgb_via_i2s(false);
    return 200;
}


void run_rgb(bool to_run) {
    using DCB = decltype(delay_callback);
    static DCB::IndexType index = DCB::INVALID_INDEX;

    if (to_run && (index == DCB::INVALID_INDEX)) {
        index = delay_callback.add_task(task_rgb_animation, 0);
    }
    else if (!to_run && (index != DCB::INVALID_INDEX)) {
        delay_callback.remove_task(index);
        index = DCB::INVALID_INDEX;
    }
}


// =======================  BUZZER  ========================

static AudioGeneratorMP3 *audio_mp3 = nullptr;
static AudioFileSourceLittleFS *audio_file = nullptr;
static AudioOutputI2SNoDAC *audio_out = nullptr;


void init_buzzer() {
    if (audio_file == nullptr) {
        audio_file = new AudioFileSourceLittleFS("/demo.mp3");
    }
    if (audio_out == nullptr) {
        audio_out = new AudioOutputI2SNoDAC();
    }
    if (audio_mp3 == nullptr) {
        audio_mp3 = new AudioGeneratorMP3();
    }

    enable_buzzer_via_i2s(true);
}


void deinit_buzzer() {
    audio_mp3->stop();
    enable_buzzer_via_i2s(false);

    if (audio_out != nullptr) {
        delete (audio_out);
        audio_out = nullptr;
    }
    if (audio_mp3 != nullptr) {
        delete (audio_mp3);
        audio_mp3 = nullptr;
    }
    if (audio_file != nullptr) {
        delete (audio_file);
        audio_file = nullptr;
    }
}


static bool _audio_finished = true;


TimeType task_audio_loop() {
    if (audio_mp3->isRunning()) {
        if (!audio_mp3->loop()) {
            audio_mp3->stop();
        }
        return 1;
    }
    else {
        serial_log.println("MP3 done");
        _audio_finished = true;
        return 0;
    }
}


bool audio_finished() {
    return _audio_finished;
}


void run_audio(bool to_run) {
    using DCB = decltype(delay_callback);
    static DCB::IndexType index = DCB::INVALID_INDEX;

    if (to_run && (index == DCB::INVALID_INDEX)) {
        index = delay_callback.add_task(task_audio_loop, 0);
        audio_mp3->begin(audio_file, audio_out);
        _audio_finished = false;
    }
    else if (!to_run && (index != DCB::INVALID_INDEX)) {
        delay_callback.remove_task(index);
        audio_mp3->stop();
        _audio_finished = true;
        index = DCB::INVALID_INDEX;
    }
}